package com.yzy.fly;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.yzy.fly.annotation.EnableFly;
import com.yzy.fly.asm.build.FlyBuilder;
import com.yzy.fly.asm.util.AsmUtil;
import com.yzy.fly.boot.FlyComponentNames;
import com.yzy.fly.doctor.boot.pageQueryCriteriaCheck.PageQueryCriteriaCheck;
import com.yzy.fly.function.logging.Log;
import com.yzy.fly.function.logging.LogFactory;
import com.yzy.fly.function.roleview.RoleAnnoLoader;
import com.yzy.fly.kernel.mapper.FlyBasicMapper;
import com.yzy.fly.kernel.model.FlyModelAnalyse;
import com.yzy.fly.test.junit.FlySpringBootTest;
import com.yzy.fly.test.junit.FlyWebSpringBootTest;
import com.yzy.fly.util.*;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.junit.runner.RunWith;
import org.reflections.Reflections;
import org.reflections.scanners.Scanner;
import org.reflections.scanners.Scanners;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.ReflectionUtils;

import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import java.util.Set;

@Slf4j
public class StartAppClassNameListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {

    @Setter
    @Getter
    private String[] scannerPaths;
    @Setter
    @Getter
    private String[] apiListenerPaths;

    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        String[] args = event.getArgs();
        log.info("args: {}", args);

        if (ArrayUtils.isNotEmpty(args)) {
            if (StringUtils.equalsIgnoreCase(FlyComponentNames.FLY_ASM_MAVEN_PLUGIN, args[0])) {
                return;
            }
        }
//        -Dfly.mode=dev
//        event.getEnvironment().getPropertySources();
        ConfigurableEnvironment env = event.getEnvironment();
        String flyMode = env.getProperty("fly.mode");
        FlyEnvContext.add("fly.mode", flyMode);

//        String applicationName = event.getApplicationContext().getApplicationName();
        Class<?> mainApplicationClass = event.getSpringApplication().getMainApplicationClass();
        Class<?> primarySourceClass = null;
        Set<Object> allSources = ((SpringApplication) event.getSource()).getAllSources();
        if (CollectionUtils.isNotEmpty(allSources)) {
            primarySourceClass = (Class<?>) allSources.iterator().next();
        }
        String startAppClassName = mainApplicationClass.getName();
        String primarySourceClassName = primarySourceClass != null ? primarySourceClass.getName() : "";
        FlyEnvContext.add("StartAppClassName", startAppClassName);
        FlyEnvContext.add("MainApplicationClass", mainApplicationClass);
        FlyEnvContext.add("PrimarySourceClassName", primarySourceClassName);
        boolean isTestClass = isTestClass(mainApplicationClass);
        EnableFly enableFly = null;
        if (isTestClass) {
            enableFly = primarySourceClass.getAnnotation(EnableFly.class);
        } else {
            enableFly = mainApplicationClass.getAnnotation(EnableFly.class);
        }
        FlyEnvContext.add("EnableFly", enableFly);
        String[] scannerPathConfigs = null;
        if (enableFly != null) {
            scannerPathConfigs = enableFly.scannerPaths();
            String[] apiListenerPathConfigs = enableFly.apiListenerPaths();
            apiListenerPaths = ArraysUtil.concat(apiListenerPaths == null ? new String[]{} : apiListenerPaths, apiListenerPathConfigs == null ? new String[]{} : apiListenerPathConfigs);
        } else {
            SpringBootApplication sbaAnno = mainApplicationClass.getAnnotation(SpringBootApplication.class);
            scannerPathConfigs = sbaAnno.scanBasePackages();
        }

        scannerPaths = scannerPathConfigs == null ? new String[]{} : scannerPathConfigs;

        FlyEnvContext.add("scannerPaths", scannerPaths);
        FlyEnvContext.add("apiListenerPaths", apiListenerPaths);

        //提前初始化Reflections
        initReflections();
        try {
            flyStartBefore(env);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // Fly 相关Bean扫描暂时放在这里
        try {
            Reflections reflections = FlyEnvContext.get("Reflections_Instance");;
            Set<Class<? extends FlyBasicMapper>> mapperClasses = reflections.getSubTypesOf(FlyBasicMapper.class);
            log.info("mapperClasses.size:{}",mapperClasses != null ? mapperClasses.size(): 0);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 判断启动类是不是测试类
     *
     * @param applicationClass
     * @return
     */
    private boolean isTestClass(Class<?> applicationClass) {
        boolean isTestClass = false;
        if (applicationClass.isAnnotationPresent(RunWith.class) || applicationClass.isAnnotationPresent(SpringBootTest.class)) {
            return true;
        }
//        List<Class> superclasses = ClassUtil.getSuperclasses(applicationClass);
//        if (superclasses.contains(FlySpringBootTest.class) || superclasses.contains(FlyWebSpringBootTest.class)) {
//            return true;
//        }
        if (FlySpringBootTest.class.isAssignableFrom(applicationClass) || FlyWebSpringBootTest.class.isAssignableFrom(applicationClass)) {
            return true;
        }
        return isTestClass;
    }

    public void flyStartBefore(ConfigurableEnvironment env) throws Exception {
        String startupId = GenerateIdUtil.getStrId();
        log.info("启动Id:{}", startupId); //打印启动ID
        FlyEnvContext.add("startupId", startupId);

        String active = env.getProperty("spring.profiles.active");
        String fileName = StringUtils.isBlank(active) ? "application.properties" : "application-" + active + ".properties";
        ClassPathResource resource = new ClassPathResource(fileName);
        FlyProperties.build(resource.getInputStream());
        FlyProperties.buildPropertiesConfiguration(resource.getInputStream());

        Properties properties = FlyEnvContext.get("applicationProperties");
        String appId = properties.getProperty("fly.configuration.appId");
        if (StringUtils.isNotBlank(appId)) {
            appId = properties.getProperty("spring.application.name");
        }
        FlyEnvContext.add("appId", appId);
        FlyModelAnalyse.execute(scannerPaths);
//        FlyBuilder.flyCoreExecute();
//        FlyBuilder.execute(scannerPaths);

        String flyMode = FlyEnvContext.get("fly.mode");
        if (StringUtils.equalsIgnoreCase("dev", flyMode)) {
            FlyEnvContext.add("asm_build_source", "dev_start");
            EnableFly enableFly = FlyEnvContext.get("EnableFly");
            if (enableFly != null) {
                String[] scannerPathConfigs = enableFly.scannerPaths();
                FlyBuilder.cacheClean();
                // 这个方法不好用，得到的urls size是0, 获取不到应用目录
//                Collection<URL> urls = ClasspathHelper.forClassLoader(ClasspathHelper.contextClassLoader());
                Collection<URL> urls = Lists.newArrayList(new File(getProjectPath()).toURI().toURL());
                FlyBuilder.execute(scannerPathConfigs, urls);
            }
        }
//        本地启动前扫描的不太准确,用requestMapping注册是加载ApiExcel比较准确
//        ExcelConfigAnalyse.execute(scannerPaths);

        RoleAnnoLoader.execute(scannerPaths);
        PageQueryCriteriaCheck.execute(scannerPaths);

        String logLogImpl = properties.getProperty("fly.configuration.log.log-impl");
        if (StringUtils.isNotBlank(logLogImpl)) {
            Class<? extends Log> logImplClass = ClassUtil.resolveClass(logLogImpl);
            if (logLogImpl != null) {
                LogFactory.useCustomLogging(logImplClass);
            }
        }
    }

    private void initReflections() {
        Reflections reflections = Reflections.collect();
        boolean isCollect = isCollect(reflections);
        FlyEnvContext.add("Reflections_IsCollect", isCollect);
        if (!isCollect) {
            String[] deafultScannerPaths = new String[]{".", "com.yzy.fly"};
            String[] merge = ArraysUtil.concat(deafultScannerPaths, null == scannerPaths ? new String[0] : scannerPaths);
            merge = Sets.newHashSet(merge).toArray(new String[]{});

            ConfigurationBuilder configurationBuilder = ConfigurationBuilder.build(merge);
            configurationBuilder.setScanners(Scanners.SubTypes, Scanners.TypesAnnotated);
            configurationBuilder.filterInputsBy(s -> s.endsWith(".class"));
            Set<URL> urls = configurationBuilder.getUrls();
            List<URL> flyUrls = AsmUtil.loadFlyUrl(urls);
            configurationBuilder.setUrls(flyUrls);

            reflections = new Reflections(configurationBuilder);
        }
        FlyEnvContext.add("Reflections_Instance", reflections);
    }

    /**
     * 判断是否存在已扫描过的文件*-reflections.xml
     * @param reflections
     * @return
     */
    public static boolean isCollect(Reflections reflections) {
        Field scannersField = ReflectionUtils.findField(ConfigurationBuilder.class, "scanners");
        scannersField.setAccessible(true);
        Set<Scanner> scanners = (Set<Scanner>)ReflectionUtils.getField(scannersField, reflections.getConfiguration());
        return CollectionUtils.isNotEmpty(scanners);
    }

    /**
     *
     * @return absolutePath + "/target/classes/";
     */
    public static String getProjectPath() {
        try {
            // 首先尝试获取当前工作目录
            String path = System.getProperty("user.dir");
            if (StringUtils.isNotBlank(path)) {
                log.info("project path: {}", path);
                return path + "/target/classes/";
            } else {
                log.info("project path is null, continue to search for the path");
            }
            // 如果不行，尝试通过类加载器获取
            Class mainApplicationClass = FlyEnvContext.get("MainApplicationClass");
            URL url = mainApplicationClass.getProtectionDomain().getCodeSource().getLocation();
            String jarPath = URLDecoder.decode(url.getPath(), StandardCharsets.UTF_8);
            File jarFile = new File(jarPath);
            log.info("project path(jar): {}", jarFile.getParentFile().getAbsolutePath());
            return jarFile.getParentFile().getAbsolutePath() + "/classes/";
        } catch (Exception e) {
            e.printStackTrace();
            throw new ApplicationContextException("getProjectPath error, please check!", e);
//            return "."; // 默认返回当前目录
        }
    }

}